﻿using System;
using System.IO;
using System.Text;
using System.Reflection;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell;
using EnvDTE;
using EnvDTE80;
using Microsoft.Win32;
using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VSSDK.Tools.VsIdeTesting;
using Microsoft.VisualStudio;

namespace Microsoft.VsSDK.IntegrationTestLibrary {
  /// <summary>
  /// </summary>
  public class TestUtils {

    #region Methods: Handling embedded resources
    /// <summary>
    /// Gets the embedded file identified by the resource name, and converts the
    /// file into a string.
    /// </summary>
    /// <param name="resourceName">In VS, is DefaultNamespace.FileName?</param>
    /// <returns></returns>
    public static string GetEmbeddedStringResource(Assembly assembly, string resourceName) {
      string result = null;

      // Use the .NET procedure for loading a file embedded in the assembly
      Stream stream = assembly.GetManifestResourceStream(resourceName);
      if (stream != null) {
        // Convert bytes to string
        byte[] fileContentsAsBytes = new byte[stream.Length];
        stream.Read(fileContentsAsBytes, 0, (int) stream.Length);
        result = Encoding.Default.GetString(fileContentsAsBytes);
      } else {
        // Embedded resource not found - list available resources
        Debug.WriteLine("Unable to find the embedded resource file '" + resourceName + "'.");
        Debug.WriteLine("  Available resources:");
        foreach (string aResourceName in assembly.GetManifestResourceNames()) {
          Debug.WriteLine("    " + aResourceName);
        }
      }

      return result;
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="embeddedResourceName"></param>
    /// <param name="baseFileName"></param>
    /// <param name="fileExtension"></param>
    /// <returns></returns>
    public static void WriteEmbeddedResourceToFile(Assembly assembly, string embeddedResourceName, string fileName) {
      // Get file contents
      string fileContents = GetEmbeddedStringResource(assembly, embeddedResourceName);
      if (fileContents == null)
        throw new ApplicationException("Failed to get embedded resource '" + embeddedResourceName + "' from assembly '" + assembly.FullName);

      // Write to file
      StreamWriter sw = new StreamWriter(fileName);
      sw.Write(fileContents);
      sw.Close();
    }

    /// <summary>
    /// Writes an embedded resource to a file.
    /// </summary>
    /// <param name="assembly">The name of the assembly that the embedded resource is defined.</param>
    /// <param name="embeddedResourceName">The name of the embedded resource.</param>
    /// <param name="fileName">The file to write the embedded resource's content.</param>
    public static void WriteEmbeddedResourceToBinaryFile(Assembly assembly, string embeddedResourceName, string fileName) {
      // Get file contents
      Stream stream = assembly.GetManifestResourceStream(embeddedResourceName);
      if (stream == null)
        throw new InvalidOperationException("Failed to get embedded resource '" + embeddedResourceName + "' from assembly '" + assembly.FullName);

      // Write to file
      BinaryWriter sw = null;
      FileStream fs = null;
      try {
        byte[] fileContentsAsBytes = new byte[stream.Length];
        stream.Read(fileContentsAsBytes, 0, (int) stream.Length);

        FileMode mode = FileMode.CreateNew;
        if (File.Exists(fileName)) {
          mode = FileMode.Truncate;
        }

        fs = new FileStream(fileName, mode);

        sw = new BinaryWriter(fs);
        sw.Write(fileContentsAsBytes);
      } finally {
        if (fs != null) {
          fs.Close();
        }
        if (sw != null) {
          sw.Close();
        }
      }
    }

    #endregion

    #region Methods: Handling temporary files and directories
    /// <summary>
    /// Returns the first available file name on the form
    ///   [baseFileName]i.[extension]
    /// where [i] starts at 1 and increases until there is an available file name
    /// in the given directory. Also creates an empty file with that name to mark
    /// that file as occupied.
    /// </summary>
    /// <param name="directory">Directory that the file should live in.</param>
    /// <param name="baseFileName"></param>
    /// <param name="extension">may be null, in which case the .[extension] part
    /// is not added.</param>
    /// <returns>Full file name.</returns>
    public static string GetNewFileName(string directory, string baseFileName, string extension) {
      // Get the new file name
      string fileName = GetNewFileOrDirectoryNameWithoutCreatingAnything(directory, baseFileName, extension);

      // Create an empty file to mark it as taken
      StreamWriter sw = new StreamWriter(fileName);

      sw.Write("");
      sw.Close();
      return fileName;
    }
    /// <summary>
    /// Returns the first available directory name on the form
    ///   [baseDirectoryName]i
    /// where [i] starts at 1 and increases until there is an available directory name
    /// in the given directory. Also creates the directory to mark it as occupied.
    /// </summary>
    /// <param name="directory">Directory that the file should live in.</param>
    /// <param name="baseDirectoryName"></param>
    /// <returns>Full directory name.</returns>
    public static string GetNewDirectoryName(string directory, string baseDirectoryName) {
      // Get the new file name
      string directoryName = GetNewFileOrDirectoryNameWithoutCreatingAnything(directory, baseDirectoryName, null);

      // Create an empty directory to make it as occupied
      Directory.CreateDirectory(directoryName);

      return directoryName;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="directory"></param>
    /// <param name="baseFileName"></param>
    /// <param name="extension"></param>
    /// <returns></returns>
    private static string GetNewFileOrDirectoryNameWithoutCreatingAnything(string directory, string baseFileName, string extension) {
      // - get a file name that we can use
      string fileName;
      int i = 1;

      string fullFileName = null;
      while (true) {
        // construct next file name
        fileName = baseFileName + i;
        if (extension != null)
          fileName += '.' + extension;

        // check if that file exists in the directory
        fullFileName = Path.Combine(directory, fileName);

        if (!File.Exists(fullFileName) && !Directory.Exists(fullFileName))
          break;
        else
          i++;
      }

      return fullFileName;
    }
    #endregion

    #region Methods: Handling solutions
    /// <summary>
    /// Closes the currently open solution (if any), and creates a new solution with the given name.
    /// </summary>
    /// <param name="solutionName">Name of new solution.</param>
    public void CreateEmptySolution(string directory, string solutionName) {
      CloseCurrentSolution(__VSSLNSAVEOPTIONS.SLNSAVEOPT_NoSave);

      string solutionDirectory = GetNewDirectoryName(directory, solutionName);

      // Create and force save solution
      IVsSolution solutionService = (IVsSolution) VsIdeTestHostContext.ServiceProvider.GetService(typeof(IVsSolution));
      solutionService.CreateSolution(solutionDirectory, solutionName, (uint) __VSCREATESOLUTIONFLAGS.CSF_SILENT);
      solutionService.SaveSolutionElement((uint) __VSSLNSAVEOPTIONS.SLNSAVEOPT_ForceSave, null, 0);
      DTE dte = VsIdeTestHostContext.Dte;
      Assert.AreEqual(solutionName + ".sln", Path.GetFileName(dte.Solution.FileName), "Newly created solution has wrong Filename");
    }

    public void CloseCurrentSolution(__VSSLNSAVEOPTIONS saveoptions) {
      // Get solution service
      IVsSolution solutionService = (IVsSolution) VsIdeTestHostContext.ServiceProvider.GetService(typeof(IVsSolution));

      // Close already open solution
      solutionService.CloseSolutionElement((uint) saveoptions, null, 0);
    }

    public void ForceSaveSolution() {
      // Get solution service
      IVsSolution solutionService = (IVsSolution) VsIdeTestHostContext.ServiceProvider.GetService(typeof(IVsSolution));

      // Force-save the solution
      solutionService.SaveSolutionElement((uint) __VSSLNSAVEOPTIONS.SLNSAVEOPT_ForceSave, null, 0);
    }

    /// <summary>
    /// Get current number of open project in solution
    /// </summary>
    /// <returns></returns>
    public int ProjectCount() {
      // Get solution service
      IVsSolution solutionService = (IVsSolution) VsIdeTestHostContext.ServiceProvider.GetService(typeof(IVsSolution));
      object projectCount;
      solutionService.GetProperty((int) __VSPROPID.VSPROPID_ProjectCount, out projectCount);
      return (int) projectCount;
    }
    #endregion

    #region Methods: Handling projects
    /// <summary>
    /// Creates a project.
    /// </summary>
    /// <param name="projectName">Name of new project.</param>
    /// <param name="templateName">Name of project template to use</param>
    /// <param name="language">language</param>
    /// <returns>New project.</returns>
    public void CreateProjectFromTemplate(string projectName, string templateName, string language, bool exclusive) {
      DTE dte = (DTE) VsIdeTestHostContext.ServiceProvider.GetService(typeof(DTE));

      Solution2 sol = dte.Solution as Solution2;
      string projectTemplate = sol.GetProjectTemplate(templateName, language);

      // - project name and directory
      string solutionDirectory = Directory.GetParent(dte.Solution.FullName).FullName;
      string projectDirectory = GetNewDirectoryName(solutionDirectory, projectName);

      dte.Solution.AddFromTemplate(projectTemplate, projectDirectory, projectName, false);
    }
    #endregion

    #region Methods: Handling project items
    /// <summary>
    /// Create a new item in the project
    /// </summary>
    /// <param name="parent">the parent collection for the new item</param>
    /// <param name="templateName"></param>
    /// <param name="language"></param>
    /// <param name="name"></param>
    /// <returns></returns>
    public ProjectItem AddNewItemFromVsTemplate(ProjectItems parent, string templateName, string language, string name) {
      if (parent == null)
        throw new ArgumentException("project");
      if (name == null)
        throw new ArgumentException("name");

      DTE dte = (DTE) VsIdeTestHostContext.ServiceProvider.GetService(typeof(DTE));

      Solution2 sol = dte.Solution as Solution2;

      string filename = sol.GetProjectItemTemplate(templateName, language);

      parent.AddFromTemplate(filename, name);

      return parent.Item(name);
    }

    /// <summary>
    /// Save an open document.
    /// </summary>
    /// <param name="documentMoniker">for filebased documents this is the full path to the document</param>
    public void SaveDocument(string documentMoniker) {
      // Get document cookie and hierarchy for the file
      IVsRunningDocumentTable runningDocumentTableService = (IVsRunningDocumentTable) VsIdeTestHostContext.ServiceProvider.GetService(typeof(IVsRunningDocumentTable));
      uint docCookie;
      IntPtr docData;
      IVsHierarchy hierarchy;
      uint itemId;
      runningDocumentTableService.FindAndLockDocument(
          (uint) Microsoft.VisualStudio.Shell.Interop._VSRDTFLAGS.RDT_NoLock,
          documentMoniker,
          out hierarchy,
          out itemId,
          out docData,
          out docCookie);

      // Save the document
      IVsSolution solutionService = (IVsSolution) VsIdeTestHostContext.ServiceProvider.GetService(typeof(IVsSolution));
      solutionService.SaveSolutionElement((uint) __VSSLNSAVEOPTIONS.SLNSAVEOPT_ForceSave, hierarchy, docCookie);
    }

    public void CloseInEditorWithoutSaving(string fullFileName) {
      // Get the RDT service
      IVsRunningDocumentTable runningDocumentTableService = (IVsRunningDocumentTable) VsIdeTestHostContext.ServiceProvider.GetService(typeof(IVsRunningDocumentTable));
      Assert.IsNotNull(runningDocumentTableService, "Failed to get the Running Document Table Service");

      // Get our document cookie and hierarchy for the file
      uint docCookie;
      IntPtr docData;
      IVsHierarchy hierarchy;
      uint itemId;
      runningDocumentTableService.FindAndLockDocument(
          (uint) Microsoft.VisualStudio.Shell.Interop._VSRDTFLAGS.RDT_NoLock,
          fullFileName,
          out hierarchy,
          out itemId,
          out docData,
          out docCookie);

      // Get the SolutionService
      IVsSolution solutionService = VsIdeTestHostContext.ServiceProvider.GetService(typeof(IVsSolution)) as IVsSolution;
      Assert.IsNotNull(solutionService, "Failed to get IVsSolution service");

      // Close the document
      solutionService.CloseSolutionElement(
          (uint) __VSSLNSAVEOPTIONS.SLNSAVEOPT_NoSave,
          hierarchy,
          docCookie);
    }
    #endregion

    #region Methods: Handling Toolwindows
    public bool CanFindToolwindow(Guid persistenceGuid) {
      IVsUIShell uiShellService = VsIdeTestHostContext.ServiceProvider.GetService(typeof(SVsUIShell)) as IVsUIShell;
      Assert.IsNotNull(uiShellService);
      IVsWindowFrame windowFrame;
      int hr = uiShellService.FindToolWindow((uint) __VSFINDTOOLWIN.FTW_fFindFirst, ref persistenceGuid, out windowFrame);
      Assert.IsTrue(hr == VSConstants.S_OK);

      return (windowFrame != null);
    }
    #endregion

    #region Methods: Loading packages
    public IVsPackage LoadPackage(Guid packageGuid) {
      IVsShell shellService = (IVsShell) VsIdeTestHostContext.ServiceProvider.GetService(typeof(SVsShell));
      IVsPackage package;
      shellService.LoadPackage(ref packageGuid, out package);
      Assert.IsNotNull(package, "Failed to load package");
      return package;
    }
    #endregion

    /// <summary>
    /// Executes a Command (menu item) in the given context
    /// </summary>
    public void ExecuteCommand(CommandID cmd) {
      object Customin = null;
      object Customout = null;
      string guidString = cmd.Guid.ToString("B").ToUpper();
      int cmdId = cmd.ID;
      DTE dte = VsIdeTestHostContext.Dte;
      dte.Commands.Raise(guidString, cmdId, ref Customin, ref Customout);
    }

  }
}
